#!/bin/bash
#
#  (clandestine.googlecode.com)  libclandestine: yet another script kiddie's bash framework
#
# Core declarations
#
readonly _LC_VERSION='0+arctic'
readonly _LC_BASE="${BASH_SOURCE%.bash}"
TMPARR=( )
_LC_INCLUDES=( )
_LC_DEBUG=0
_LC_QUIET=0

######################################################################
#
# Core error reporting
#
function _lc_error_message () { (( $# > 0 )) || return 0
	declare -- LEVEL="$1"  MESSAGE
	shift
	(( $# > 0 )) && MESSAGE=": $@"
	echo "${LEVEL}${MESSAGE}." > /dev/stderr
}
function Debug () { (( _LC_DEBUG )) && _lc_error_message 'NOTICE' "$@"; return 0; }
function Warn () { (( _LC_QUIET )) || _lc_error_message 'WARNING' "$@"; }
function Die () {
	_lc_error_message 'FATAL' "$@"
	if (( _LC_DEBUG )); then
		Debug "dumping stack trace (<line no.> <function name> <file name>)"
		declare -i FRAME=0
		while caller "$FRAME"; do (( FRAME+=1 )); done
	fi
	exit 255
}

######################################################################
#
# Core filesystem functions
#
function BaseName () { _LC_REPLY=(); (( $# > 0 )) || return 0
	declare -- FILENAME
	for FILENAME; do _LC_REPLY+=( "${FILENAME##*/}" ); done
}
function DirName () { _LC_REPLY=(); (( $# > 0 )) || return 0
	declare -- FILENAME
	for FILENAME; do _LC_REPLY+=( "${FILENAME%/*}" ); done
}
function CleanPath () { _LC_REPLY=(); (( $# > 0 )) || return 0
	declare -- FILENAME  ELEMENT  NEWPATH
	declare -a ELEMENTS  NEWELEMENTS
	for FILENAME; do
		[[ '/' == "${FILENAME:0:1}" || '~' == "${FILENAME:0:1}" ]] || FILENAME="`pwd -P`/$FILENAME"
		IFS='/' read -a ELEMENTS <<< "$FILENAME"
		NEWELEMENTS=( )
		if [[ "${ELEMENTS[0]}" ]]; then
			NEWPATH="${ELEMENTS[0]}"
			unset ELEMENTS[0]
		else NEWPATH=''; fi
		(( ${#ELEMENTS[@]} )) && for ELEMENT in "${ELEMENTS[@]}"; do
			[[ "$ELEMENT" && '.' != "$ELEMENT" ]] || continue
			if [[ '..' == "$ELEMENT" ]]; then
				(( ${#NEWELEMENTS[@]} )) && unset "NEWELEMENTS[${#NEWELEMENTS[@]} - 1]"
			else NEWELEMENTS+=( "$ELEMENT" ); fi
		done
		(( ${#NEWELEMENTS[@]} )) && for ELEMENT in "${NEWELEMENTS[@]}"; do NEWPATH+="/$ELEMENT"; done
		[[ '/' == "${FILENAME: -1:1}" ]] && NEWPATH+='/'
		_LC_REPLY+=( "$NEWPATH" )
	done
}

######################################################################
#
# Core include functions
#
function _lc_includes_check () { (( $# > 0 )) || return 255
	declare -- INCLUDE
	(( ${#_LC_INCLUDES[@]} )) && for INCLUDE in "${_LC_INCLUDES[@]}"; do if [[ "$1" == "$INCLUDE" ]]; then return 0; fi; done
	return 255
}
function _lc_includes_push () { (( $# > 0 )) || return 0
	declare -- FNAME
	for FNAME; do _lc_includes_check "$FNAME" || _LC_INCLUDES+=( "$FNAME" ); done;
}
function _lc_include () { (( $# > 2 )) || return 0
	declare -- REQUIRE="$1"  ONCE="$2"  SOURCE
	shift 2
	CleanPath "$@"
	(( ${#_LC_REPLY[@]} )) && for SOURCE in "${_LC_REPLY[@]}"; do
		[[ "$ONCE" ]] && _lc_includes_check "$SOURCE" && return 0
		if [[ -r "$SOURCE" ]]; then
			_lc_includes_push "$SOURCE"
			if ! source "$SOURCE"; then
				if [[ "$REQUIRE" ]]; then
					Die "error while including file [$SOURCE], aborting"
				else Warn "error while including file [$SOURCE], ignoring"; fi
			fi
		else
			if [[ "$REQUIRE" ]]; then
				Die "can't include file [$SOURCE] (read test failed), aborting"
			else Warn "can't include file [$SOURCE] (read test failed), skipping"; fi
		fi
	done
	return 0
}
function Include ()     { _lc_include ''    ''    "$@"; }
function IncludeOnce () { _lc_include ''    'YES' "$@"; }
function Require ()     { _lc_include 'YES' ''    "$@"; }
function RequireOnce () { _lc_include 'YES' 'YES' "$@"; }
function Use () { (( $# > 0 )) || return 0
	declare -- MODULE
	for MODULE; do
		BaseName "$MODULE"
		RequireOnce "${_LC_BASE}-${_LC_REPLY}.bash"
	done
}

######################################################################
#
# Autorun
#
set -eu # be strict
set +H  # disable history expansion

if (( ${#BASH_SOURCE[@]} > 1 )); then
	CleanPath "${BASH_SOURCE[@]}"
	(( ${#_LC_REPLY[@]} )) && _lc_includes_push "${_LC_REPLY[@]}"
else Warn 'please source this file from your script, do not run it directly'; fi
